﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SharedGenomics.Workbench.Core;
using SharedGenomics.Workbench.Views;
using SharedGenomics.Workbench.PresentationModel;
using SharedGenomics.Workbench.Services;
using SharedGenomics.Domain;
using SharedGenomics.Domain.Builders;
using SharedGenomics.DataTransfer;

namespace SharedGenomics.Workbench.Presenters
{
    public class DataFilterPresenter : Presenter
    {
        private int _loadedFileSetId;

        //TODO: remove this reference - this is required since there's no sensible place to put the fileset distribution summary except on a fileset DTO
        //due to time constraints, a separate call must be made to the service to get the summary stats separately and set on the filter parameters presenter
        private readonly IDataService _dataService;

        public DataFilterPresenter(IDataFilterView view, IFileSetRepository fileSetRepository, IDataSetRepository dataSetRepository, IDataFilterService filterService, DataFilterParametersPresenter parametersPresenter, DataFilterResultsPresenter resultsPresenter, ICursorManager cursorManager, IDataService dataService)
            : base(view)
        {
            //initialse view
            view.IsLoadProgressBarVisible = false;

            //register for view events
            view.Loaded += new EventHandler(this.View_Loaded);
            view.LoadFileSetClicked += new EventHandler(this.View_LoadFileSetClicked);
            view.RefreshFileSetsClicked += new EventHandler(this.View_RefreshFileSetsClicked);
            view.CreateDataSetClicked += new EventHandler(this.View_CreateDataSetClicked);

            //register for filter events
            parametersPresenter.FilterStarted += new EventHandler<DataFilterEventArgs>(this.ParametersPresenter_FilterStarted);

            this.FilterView = view;
            this.FileSetRepository = fileSetRepository;
            this.DataSetRepository = dataSetRepository;
            this.FilterService = filterService;
            this.ParametersPresenter = parametersPresenter;
            this.FilterResultsPresenter = resultsPresenter;
            this.CursorManager = cursorManager;
            this._dataService = dataService;
        }

        private void View_CreateDataSetClicked(object sender, EventArgs e)
        {
            bool ptoValid = this.FilterPTO.Validate();

            FilterParameterSetInfo parameters;
            if (ptoValid && this.ParametersPresenter.TryCreateParameterSet(out parameters))
            {
                var dataSetInfo = new DataSetInfoBuilder()
                    .Called(this.FilterPTO.DataSetName)
                    .ForFileSet(this._loadedFileSetId)
                    .WithParameters(parameters)
                    .Build();

                try
                {
                    this.DataSetRepository.AddDataSet(dataSetInfo);
                    this.FilterView.DataSetSaveMessage = String.Format("Data set '{0}' saved successfully", dataSetInfo.Name);
                }
                catch (Exception ex)
                {
                    this.FilterView.DataSetSaveMessage = String.Empty;
                    this.FilterView.DisplayError(String.Format("Unable to save data set:{0}{1}", Environment.NewLine, ex.Message));
                }
            }
        }

        private void ParametersPresenter_FilterStarted(object sender, DataFilterEventArgs e)
        {
            //clear existing filter results and begin new filter run
            this.ParametersPresenter.CanFilter = false;
            this.CursorManager.SetBusy(this.View);

            this.FilterResultsPresenter.ClearResults();

            this.BackgroundExecutor.Execute(() => 
            {
                return this.FilterService.ApplyFilters(this._loadedFileSetId, e.ParameterSet, this.FilterResultsPresenter.WatchListPresenter.WatchList);
            },
            or =>
            {
                this.ParametersPresenter.CanFilter = true;
                this.CursorManager.SetDefault(this.View);

                if (or.Error == null)
                {
                    this.FilterResultsPresenter.FilterResultsAvailable(or.Result);
                }
                else
                {
                    this.View.DisplayError(String.Format("Unable to get filter results: {0}", or.Error.Message));
                }
            });
        }

        private void View_RefreshFileSetsClicked(object sender, EventArgs e)
        {
            this.LoadFileSets();
        }

        private void View_LoadFileSetClicked(object sender, EventArgs e)
        {
            this._loadedFileSetId = this.FilterPTO.SelectedFileSetId.Value;
            this.FilterResultsPresenter.ClearResults();
            this.ParametersPresenter.ClearFilters();

            this.FilterView.IsLoadEnabled = false;
            this.FilterView.IsLoadProgressBarVisible = true;

            this.BackgroundExecutor.Execute(() =>
            {
                var fileSet = this.FileSetRepository.GetById(this._loadedFileSetId);
                var distributionSummary = this._dataService.GetFileSetDataDistribution(this._loadedFileSetId);
                return new object[] { fileSet, distributionSummary };
            },
            or =>
            {
                this.FilterView.IsLoadEnabled = true;
                this.FilterView.IsLoadProgressBarVisible = false;

                if (or.Error == null)
                {
                    var fileSetMaybe = (Maybe<FileSetInfo>)or.Result[0];
                    var distSummaryMaybe = (Maybe<FileSetDataDistributionSummary>)or.Result[1];

                    fileSetMaybe.BindAction(fileSet =>
                    {
                        this.ParametersPresenter.Schema = fileSet.Schema;

                        //NOTE: we assume that the summary call has also succeeded if the fileset was fetched
                        this.ParametersPresenter.DataDistribution = distSummaryMaybe.Value;

                        this.FilterView.LoadedFileSetName = fileSet.Name;
                    });
                }
                else
                {
                    this.View.DisplayError(String.Format("Unable to load file set: {0}", or.Error.Message));
                }
            });
        }

        private void View_Loaded(object sender, EventArgs e)
        {
            //create and set PTO for view
            this.FilterPTO = new DataFilterPTO();
            this.FilterPTO.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.FilterPTO_PropertyChanged);
            this.FilterPTO.ValidationChanged += (o, vea) => { this.View.ValidationErrors = vea.ValidationErrors; };

            this.FilterView.PTO = this.FilterPTO;

            this.FilterView.IsLoadEnabled = false;
            this.LoadFileSets();
        }

        private void FilterPTO_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "SelectedFileSetId")
            {
                this.FilterView.IsLoadEnabled = this.FilterPTO.SelectedFileSetId.HasValue;
            }
        }

        private void LoadFileSets()
        {
            var fileSets = this.FileSetRepository.GetUserFileSets();
            fileSets.BindAction(fSets =>
            {
                this.FilterPTO.AvailableFileSets = fSets.Select(fsi => new ValueDisplayItem<int>(fsi.Id, fsi.Name));
            });
        }

        private IDataFilterView FilterView { get; set; }
        private IFileSetRepository FileSetRepository { get; set; }
        private IDataSetRepository DataSetRepository { get; set; }
        private DataFilterPTO FilterPTO { get; set; }
        private IDataFilterService FilterService { get; set; }
        private ICursorManager CursorManager { get; set; }

        [Region("CategoryFilters")]
        public DataFilterParametersPresenter ParametersPresenter { get; private set; }

        [Region("FilterResults")]
        public DataFilterResultsPresenter FilterResultsPresenter { get; private set; }
    }
}
